﻿namespace Pyramid.Text
{
    using System;
    using System.Collections.Generic;
    using Pyramid.Utility;

    public class Tcc3Encoder
        : Encoder
    {
        private const int CHARACTERS_PER_TRYTE = 3;

        private List<char> characterBuffer = new List<char>();

        public override int GetTryteCount(char[] chars, int index, int count, bool flush)
        {
            Validate.IsLongEnough(chars, index + count, "chars");

            int characterCount = count + characterBuffer.Count;

            int tryteCount = characterCount / CHARACTERS_PER_TRYTE;

            if (flush && ((characterCount % CHARACTERS_PER_TRYTE) > 0))
            {
                tryteCount++;
            }

            return tryteCount;
        }

        public override int GetTrytes(char[] chars, int charIndex, int charCount, Tryte[] trytes, int tryteIndex, bool flush)
        {
            int trytecount = GetTryteCount(chars, charIndex, charCount, flush);

            Validate.IsLongEnough(trytes, tryteIndex + trytecount, "trytes");

            var temporaryBuffer = new List<char>(characterBuffer);

            for (; charIndex < charCount; charIndex++)
            {
                char currentCharacter = chars[charIndex];

                CheckCharacterIsValid(currentCharacter);

                temporaryBuffer.Add(currentCharacter);

                if (temporaryBuffer.Count >= CHARACTERS_PER_TRYTE)
                {
                    TransferTryte(temporaryBuffer, CHARACTERS_PER_TRYTE, trytes, tryteIndex);

                    tryteIndex++;
                }
            }

            if (flush && temporaryBuffer.Count > 0)
            {
                TransferTryte(temporaryBuffer, temporaryBuffer.Count, trytes, tryteIndex);
            }

            characterBuffer = temporaryBuffer;

            return trytecount;
        }

        public override void Reset()
        {
            this.characterBuffer.Clear();

            base.Reset();
        }

        private static void TransferTryte(List<char> characterBuffer, int characterCount, Tryte[] trytes, int tryteIndex)
        {
            trytes[tryteIndex] = GetTryte(characterBuffer, characterCount);

            characterBuffer.Clear();
        }

        private static void CheckCharacterIsValid(char currentCharacter)
        {
            if ((currentCharacter < 'A' || currentCharacter > 'Z') && currentCharacter != ' ')
            {
                // TODO Replace this with the proper Encoding Fallback mechanism
                throw new Exception("Character not in TCC3 character set.");
            }
        }

        private static Tryte GetTryte(List<char> characterBuffer, int count)
        {
            Tryte result = 0;

            for (int trybbleIndex = 0; trybbleIndex < count; trybbleIndex++)
            {
                result += (Tryte)(GetTrybble(characterBuffer[trybbleIndex]) * Math.Pow(27, trybbleIndex));
            }

            return result;
        }

        private static Trybble GetTrybble(char character)
        {
            if (character == ' ')
            {
                return 0;
            }
            else
            {
                return (Trybble)((byte)character - 64);
            }
        }
    }
}
